//
// Created by QU on 24-6-7.
//
#include <iostream>
#include <fstream>
#include <sstream>
#include <functional>
#include <utility>
#include <memory>
#include <vector>
#include <map>
#include <set>

using std::cout, std::cin, std::endl, std::unitbuf;
// 在引用类型中（无论是左值还是右值），const总是底层的。

// 参数转发
template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2){
    f(t2, t1);
}

// 这个函数一般情况下工作得很好，但当我们希望用它调用一个接受引用参数的函数时就会出现问题：
void f(int v1, int &v2){        // v2是一个引用
    std::cout << v1 << " " << ++v2 << std::endl;
}

template <typename F, typename T1, typename T2>
void flip2(F f, T1&& t1, T2&& t2){
    f(t2, t1);
}

void g(int && i, int & j)
{
    std::cout << i << " " << ++j << std::endl;
}

    // 最终版本
// 当用于一个指向模板参数类型的右值引用函数参数(T&&) 时， forward会保持持参类型的所有细节。

template <typename F, typename T1, typename  T2>
void flip(F f, T1 t1, T2 t2)
{
    f(std::forward<T1>(t1), std::forward<T2>(t2));
}

int main(int argc, char const *argv[]) {
    std::cout << "flip1: " ;
    int i =10;
    flip1(f, i, 20);
    std::cout << "i = " << i << std::endl;

    // F 被推导为 void(*)(int, int&)，即 f 函数类型。
    // T1 被推导为 int，因为 i 是左值。
    // T2 被推导为 int，因为 20 是右值。
    // void flip1( void(*fcn)(int,int&), int t1, int t2);
    //    你会发现函数的一个参数是带引用的, 但是传递的两个值是拷贝值, 这就会导致函数最终调用的时候使用的是拷贝的值, ++并不能修改i
    //    达不到我们想要使用函数模版交换两个值的同时引用依然生效的要求.

    std::cout << "flip2: " ;
    int j = 10;
    flip2(f, j, 20);
    std::cout << "j = " << j << std::endl;
    // F 被推导为 void(*)(int&&, int&)，即 g 函数类型。
    // T1 被推导为 int&，因为 i 是左值。 (int& &&折叠后是int &)
    // T2 被推导为 int，因为 42 是右值。
    // void flip1( void(*fcn)(int,int&), int& t1, int t2);
    // f交换两个变量的位置, 传递的j为int &, 改变t1最终会改变j
    // 解决了一半的问题, 但是上面的函数不能传递g函数(下面)

    // std::cout << "flip2 with g(): " ;
    // 我们将函数f换成g, g的参数有点不同. 最终会无法调用.
    // flip2(g,i , 42);
    //  cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
    // 30 |     f(t2, t1);
    //    |     ~^~~~~~~~

    // 我们可以使用std::forward保持参数信息 forward返回该显示实参类型的右值引用. forward<T>返回T&&.
    // 如果实参是一个右值， 则 Type 是一个普通 （非引用）类型，forward<Type>将返回Type&&。
    // 如果实参是一个左值，则通过引用折叠， Type 本身是一个左值引用类型。 在此
    // 情况下， 返回类型是一个指向左值引用类型的右值引用。 再次对forward<Type>的返回
    // 类型进行引用折叠， 将返回一个左值引用类型


    return 0;
}
